﻿using System;
using System.Collections.Generic;
using System.Text;
using HBDotNet;
using System.IO.Ports;
using System.Xml;
using System.Windows.Forms;

namespace CurrentCost
{
    [HBDevice("Current Cost Energy Monitor")]
    public class CurrentCost : HBDeviceBase
    {
        private SerialPort currentCostPort;
        
        double d24 = 0; 
        double d22 = 0; 
        double d20 = 0; 
        double d18 = 0; 
        double d16 = 0; 
        double d14 = 0; 
        double d12 = 0; 
        double d10 = 0; 
        double d8 = 0; 
        double d6 = 0; 
        double d4 = 0;
        double d2 = 0;

        int DayNumber = DateTime.Now.DayOfYear;

        public override bool OnInit(bool enabled)
        {
            if (enabled) OnEnable();
            return base.OnInit(enabled);
        }

        public override void OnShutdown()
        {
            if (currentCostPort != null) currentCostPort.Close();
            base.OnShutdown();
        }

        public override bool OnEnable()
        {
            if (!OpenPort(GetProperty("COM Port")))
            {
                HBModule.TraceDeviceMessage(_deviceHandle, TRC_TYPE.ttError, "Unable to open the COM port");
                return false;
            }
            return true;
        }        

        public override bool OnDisable()
        {
            if (currentCostPort != null) currentCostPort.Close();
            return base.OnDisable();
        }

        public override void OnAboutBox()
        {
            MessageBox.Show("This plugin implements a controller for a Current Cost energy monitor (using an RS-232 connection).");
        }

        protected override bool OnPropertyChangeRequest(HBDotNet.HBProperty hbProperty, string newValue)
        {
            if (hbProperty.Name == "COM Port")
            {
                if (currentCostPort != null) currentCostPort.Close();
                if (!OpenPort(newValue))
                {
                    HBModule.TraceDeviceMessage(_deviceHandle, TRC_TYPE.ttError, "Unable to open the COM port");
                    return false;
                }
            }

            return base.OnPropertyChangeRequest(hbProperty, newValue);
        }

        public override bool OnRegisterProperties()
        {
            Properties.Add(new HBProperty("COM Port", "COM Port", true, "COM5", eIOType.ioInputAndOutput, true, true, true, false, ePropertyType.ptAlpha, "Enter the COM port used to communicate with the Current Cost device (i.e. COM4)"));
            //HBModule.CreateDevicePropertyValue("COM Port", "");
            //foreach (string port in SerialPort.GetPortNames())
            //{
            //    HBModule.CreateDevicePropertyValue("COM Port", port);
            //}

            Properties.Add(new HBProperty("Software Version", "Software Version", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Days Alive", "Days Alive", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Last Update", "Last Update", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptAlpha, ""));
            Properties.Add(new HBProperty("Temperature", "Temperature", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));

            Properties.Add(new HBProperty("Electric 1 Watts", "Electric 1 Watts", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric 2 Watts", "Electric 2 Watts", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric 3 Watts", "Electric 3 Watts", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric Total Watts", "Electric Total Watts", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric Change Watts", "Electric Change Watts", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));

            Properties.Add(new HBProperty("Electric Yesterday Evening", "Electric Yesterday Evening", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric Yesterday Daytime", "Electric Yesterday Daytime", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric Yesterday Night", "Electric Yesterday Night", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));

            Properties.Add(new HBProperty("Electric Used Day 1", "Electric Used Day 1", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric Used Day 2", "Electric Used Day 2", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric Used Day 3", "Electric Used Day 3", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric Used Day 4", "Electric Used Day 4", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric Used Day 5", "Electric Used Day 5", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric Used Day 6", "Electric Used Day 6", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric Used Day 7", "Electric Used Day 7", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));

            Properties.Add(new HBProperty("Electric Used Month 1", "Electric Used Month 1", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric Used Month 2", "Electric Used Month 2", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            Properties.Add(new HBProperty("Electric Used Month 3", "Electric Used Month 3", false, "", eIOType.ioInputOnly, true, true, true, false, ePropertyType.ptNumeric, ""));
            
            return base.OnRegisterProperties();
        }

        public void SetProperty(string propertyName, string propertyValue)
        {
            if (Properties.GetPropertyByName(propertyName).CurrentValue != propertyValue)
            {
                Properties.GetPropertyByName(propertyName).CurrentValue = propertyValue;
            }
        }

        public void SetProperty(string propertyName, double propertyValue)
        {
            if (Properties.GetPropertyByName(propertyName).CurrentValue != propertyValue.ToString())
            {
                Properties.GetPropertyByName(propertyName).CurrentValue = propertyValue.ToString();
            }
        }

        public void SetProperty(string propertyName, double propertyValue, int decimalPrecision)
        {
            if (Properties.GetPropertyByName(propertyName).CurrentValue != propertyValue.ToString("F" + decimalPrecision.ToString()))
            {
             Properties.GetPropertyByName(propertyName).CurrentValue = propertyValue.ToString("F" + decimalPrecision.ToString());
            }
        }

        public string GetProperty(string propertyName)
        {
            return Properties.GetPropertyByName(propertyName).CurrentValue;
        }
        /// <summary>
        /// Method to open com port and setup Data Received Event
        /// </summary>
        /// <param name="comPort">Com Port</param>
        /// <returns></returns>
        private bool OpenPort(string comPort)
        {
            if (currentCostPort == null || !currentCostPort.IsOpen)
            {
                try
                {
                    currentCostPort = new SerialPort(comPort, 57600, Parity.None, 8, StopBits.One);
                    currentCostPort.DataReceived += new SerialDataReceivedEventHandler(currentCostPort_DataReceived);
                    currentCostPort.Open();
                    return true;
                }
                catch (Exception ex)
                {
                    HBModule.TraceDeviceMessage(_deviceHandle, TRC_TYPE.ttError, ex.Message);
                    return false;
                }
            }
            return true;
        }
        /// <summary>
        /// Event that is executed when xml data is recieved from Current Cost Unit
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void currentCostPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            ParseXml(currentCostPort.ReadLine());
        }
        /// <summary>
        /// Method parses the Current Cost XML
        /// </summary>
        /// <param name="xml"></param>
        private void ParseXml(string xml)
        {
            // Code has been writted in accordance to the description 
            // issued 18th February 2009 at http://www.currentcost.com/cc128/xml.htm

            XmlDocument xDoc = new XmlDocument();
            xDoc.LoadXml(xml);
            // xml packets always contain this data
            SetProperty("Software Version", GetValue(xDoc, "//src"));
            SetProperty("Days Alive", GetValue(xDoc, "/msg/dsb"));
            SetProperty("Last Update", GetValue(xDoc, "/msg/time"));

            // If Xml contains real-time data
            if (GetValue(xDoc, "/msg/type").Equals("1"))
            {
                SetProperty("Temperature", double.Parse(GetValue(xDoc, "/msg/tmpr", "0")), 1);

                // if sensor = 0 then whole house electricty
                if (GetValue(xDoc, "/msg/sensor").Equals("0"))
                {
                    //Get last reading from Current Electric Property
                    double dLastReading = double.Parse(GetProperty("Electric Total Watts"));

                    double dElec1 = double.Parse(GetValue(xDoc, "/msg/ch1/watts", "0"));
                    double dElec2 = double.Parse(GetValue(xDoc, "/msg/ch2/watts", "0"));
                    double dElec3 = double.Parse(GetValue(xDoc, "/msg/ch3/watts", "0"));

                    SetProperty("Electric 1 Watts", dElec1, 0);
                    SetProperty("Electric 2 Watts", dElec2, 0);
                    SetProperty("Electric 3 Watts", dElec3, 0);
                    SetProperty("Electric Total Watts", dElec1 + dElec2 + dElec3, 0);
                    SetProperty("Electric Change Watts", (dElec1 + dElec2 + dElec3) - dLastReading, 0);
                }
                else
                {
                    //Xml is for application modules, at time of coding they did not exist
                }
            }
            // Else xml must contain historical information
            else
            {
                // If Xml return Two-Hourly and h001 chunk then calculate Yesterday usage
                if (!GetValue(xDoc, "/msg/hist/data[sensor='0']/h024").Equals(string.Empty))
                {
                    d24 = double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/h024", "0"));
                }
                if (!GetValue(xDoc, "/msg/hist/data[sensor='0']/h022").Equals(string.Empty))
                {
                    d22 = double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/h022", "0"));
                }
                if (!GetValue(xDoc, "/msg/hist/data[sensor='0']/h020").Equals(string.Empty))
                {
                    d20 = double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/h020", "0"));
                }
                if (!GetValue(xDoc, "/msg/hist/data[sensor='0']/h018").Equals(string.Empty))
                {
                    d18 = double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/h018", "0"));
                }
                if (!GetValue(xDoc, "/msg/hist/data[sensor='0']/h016").Equals(string.Empty))
                {
                    double d16 = double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/h016", "0"));
                }
                if (!GetValue(xDoc, "/msg/hist/data[sensor='0']/h014").Equals(string.Empty))
                {
                    d14 = double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/h014", "0"));
                }
                if (!GetValue(xDoc, "/msg/hist/data[sensor='0']/h012").Equals(string.Empty))
                {
                    d12 = double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/h012", "0"));
                }
                if (!GetValue(xDoc, "/msg/hist/data[sensor='0']/h010").Equals(string.Empty))
                {
                    double d10 = double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/h010", "0"));
                }
                if (!GetValue(xDoc, "/msg/hist/data[sensor='0']/h008").Equals(string.Empty))
                {
                    d8 = double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/h008", "0"));
                }
                if (!GetValue(xDoc, "/msg/hist/data[sensor='0']/h006").Equals(string.Empty))
                {
                    d6 = double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/h006", "0"));
                }
                if (!GetValue(xDoc, "/msg/hist/data[sensor='0']/h004").Equals(string.Empty))
                {
                    d4 = double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/h004", "0"));
                }
                if (!GetValue(xDoc, "/msg/hist/data[sensor='0']/h002").Equals(string.Empty))
                {
                    d2 = double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/h002", "0"));
                }
                // If XML is daily
                if (!GetValue(xDoc, "/msg/hist/data/d001").Equals(string.Empty))
                {
                    SetProperty("Electric Used Day 1", double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/d001", "0")), 3);
                }
                if (!GetValue(xDoc, "/msg/hist/data/d002").Equals(string.Empty))
                {
                    SetProperty("Electric Used Day 2", double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/d002", "0")), 3);
                }
                if (!GetValue(xDoc, "/msg/hist/data/d003").Equals(string.Empty))
                {
                    SetProperty("Electric Used Day 3", double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/d003", "0")), 3);
                }
                if (!GetValue(xDoc, "/msg/hist/data/d004").Equals(string.Empty))
                {
                    SetProperty("Electric Used Day 4", double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/d004", "0")), 3);
                }
                if (!GetValue(xDoc, "/msg/hist/data/d005").Equals(string.Empty))
                {
                    SetProperty("Electric Used Day 5", double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/d005", "0")), 3);
                }
                if (!GetValue(xDoc, "/msg/hist/data/d006").Equals(string.Empty))
                {
                    SetProperty("Electric Used Day 6", double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/d006", "0")), 3);
                }
                if (!GetValue(xDoc, "/msg/hist/data/d007").Equals(string.Empty))
                {
                    SetProperty("Electric Used Day 7", double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/d007", "0")), 3);
                }
                // If XML is monthly
                if (!GetValue(xDoc, "/msg/hist/data/m001").Equals(string.Empty))
                {
                    SetProperty("Electric Used Month 1", double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/m001", "0")), 3);
                }
                if (!GetValue(xDoc, "/msg/hist/data/m002").Equals(string.Empty))
                {
                    SetProperty("Electric Used Month 2", double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/m002", "0")), 3);
                }
                if (!GetValue(xDoc, "/msg/hist/data/m003").Equals(string.Empty))
                {
                    SetProperty("Electric Used Month 3", double.Parse(GetValue(xDoc, "/msg/hist/data[sensor='0']/m003", "0")), 3);
                }
            }
           
            // If new day then calculate Night, Daytime and Evening numbers
            if (!DayNumber.Equals(DateTime.Now.DayOfYear))
            {               
                //Evening Calc
                SetProperty("Electric Yesterday Evening", d2 + d4 + d6 + d8, 3);
                //Day Calc
                SetProperty("Electric Yesterday Daytime",  d10 + d12 + d14 + d16, 3);
                //Night Calc
                SetProperty("Electric Yesterday Night", d18 + d20 + d22 + d24, 3);
                //Reset new day flag
                DayNumber = DateTime.Now.DayOfYear;
            }

        }
        /// <summary>
        /// Parses Xml using xpath and returns inner xml
        /// </summary>
        /// <param name="xDoc">XmlDocument to parse</param>
        /// <param name="xpath">XPath statement to use</param>
        /// <returns>InnerXml string</returns>
        private string GetValue(XmlDocument xDoc, string xpath)
        {
            return GetValue(xDoc, xpath, string.Empty);
        }
        /// <summary>
        /// Parses Xml using xpath and returns inner xml
        /// </summary>
        /// <param name="xDoc">XmlDocument to parse</param>
        /// <param name="xpath">XPath statement to use</param>
        /// <param name="returnValue">String that will be returned if null or error</param>
        /// <returns>InnerXml string</returns>
        private string GetValue(XmlDocument xDoc, string xpath, string returnValue)
        {
            try
            {
                return xDoc.SelectSingleNode(xpath).InnerXml;
            }
            catch
            {
                return returnValue;
            }
        }
    }
}
